package com.yuqih.common.service.view.impl;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.annotation.PostConstruct;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.beanutils.expression.DefaultResolver;
import org.apache.commons.beanutils.expression.Resolver;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import org.springframework.util.ReflectionUtils;

import com.fasterxml.jackson.core.type.TypeReference;
import com.yuqih.common.Global;
import com.yuqih.common.exception.CodedException;
import com.yuqih.common.service.view.IViewBuildRegistry;
import com.yuqih.common.service.view.IViewBuilder;
import com.yuqih.common.service.view.IViewConverter;
import com.yuqih.common.service.view.IViewConverter.Context;

@Service
public class DefaultViewBuilders {

	private static final String separator = ":";
	private static final String defaultSearchPath = "classpath*:view/*.json";

	private static final Resolver resolver = new DefaultResolver();
	private String searchPath;

	private static final TypeReference<List<ViewDefinition>> typeReference = new TypeReference<List<ViewDefinition>>() {
	};

	@Autowired(required = false)
	private List<IViewConverter> viewConverters;

	@Autowired
	private IViewBuildRegistry viewBuildRegistry;

	@SuppressWarnings({ "unchecked", "rawtypes" })
	@PostConstruct
	public void init() throws IOException, ClassNotFoundException {

		if (StringUtils.isBlank(searchPath)) {
			searchPath = defaultSearchPath;
		}

		Resource[] resources = Global.resourcePatternResolver.getResources(searchPath);
		for (Resource resource : resources) {
			List<ViewDefinition> viewDefinitions;
			InputStream ins = null;
			try {
				ins = resource.getInputStream();
				viewDefinitions = Global.objectMapper.readValue(ins, typeReference);
			} catch (Exception ex) {
				// TODO 记录日志
				throw CodedException.from(ex);
			} finally {
				IOUtils.closeQuietly(ins);
			}

			for (ViewDefinition viewDefinition : viewDefinitions) {
				Class type = Class.forName(viewDefinition.type);
				if (viewDefinition.standalone != null) {
					viewBuildRegistry.register(type, viewDefinition.viewName,
							new StandaloneBuilder(type, viewDefinition.standalone));
				} else {
					viewBuildRegistry.register(type, viewDefinition.viewName,
							new MapBuilder(type, viewDefinition.mapping));
				}
			}

		}
	}

	private IViewConverter getViewConverter(String alias) {
		if (viewConverters == null) {
			throw new IllegalArgumentException();
		}

		for (IViewConverter viewConverter : viewConverters) {
			if (StringUtils.equals(alias, viewConverter.alias())) {
				return viewConverter;
			}
		}

		throw new IllegalArgumentException();
	}

	private ViewConverterHolder getConverterHolder(String converter) {
		if (StringUtils.isBlank(converter)) {
			return null;
		}

		String[] array = StringUtils.split(converter, separator, 2);
		if (array == null || array.length == 0) {
			throw new IllegalArgumentException();
		}

		ViewConverterHolder holder = new ViewConverterHolder();
		holder.viewConverter = getViewConverter(array[0]);
		if (array.length == 2) {
			holder.param = array[1];
		}
		return holder;
	}

	private Context getContext(Class<?> type, String fieldName, String param) {

		while (resolver.hasNested(fieldName)) {
			final String next = resolver.next(fieldName);
			// TODO 用getter去找fieldType
			Field field = ReflectionUtils.findField(type, next);
			Class<?> fieldType = field.getType();
			fieldName = resolver.remove(fieldName);
			return getContext(fieldType, fieldName, param);
		}
		boolean isList = false;
		Class<?> listElementType = null;

		// TODO 用getter去找fieldType
		Field field = ReflectionUtils.findField(type, fieldName);
		Class<?> fieldType = field.getType();
		if (List.class.isAssignableFrom(fieldType)) {
			isList = true;
			// Collections.row
			ParameterizedType parameterizedType = (ParameterizedType) field.getGenericType();
			listElementType = (Class<?>) parameterizedType.getActualTypeArguments()[0];
		}

		Context context;
		if (isList) {
			context = new Context(isList, listElementType, param);
		} else {
			context = new Context(isList, fieldType, param);
		}
		return context;
	}

	/**
	 * @param searchPath
	 *            the searchPath to set
	 */
	public void setSearchPath(String searchPath) {
		this.searchPath = searchPath;
	}

	private class StandaloneBuilder implements IViewBuilder<Object> {

		private final Class<?> type;
		private final ViewFieldDefinition definition;

		public StandaloneBuilder(Class<?> type, ViewFieldDefinition definition) {
			super();
			this.type = type;
			this.definition = definition;
		}

		@Override
		public Object build(Object source) {

			String fieldName = definition.getField();

			Object property;
			try {
				property = PropertyUtils.getProperty(source, fieldName);
			} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
				throw new RuntimeException(e);
			}

			ViewConverterHolder holder = getConverterHolder(definition.getConverter());
			if (holder == null) {
				return property;
			}

			Context context = getContext(type, fieldName, holder.param);

			return holder.viewConverter.convert(property, context);

		}

	}

	private class MapBuilder implements IViewBuilder<Object> {

		private final Class<?> type;
		private final Map<String, ViewFieldDefinition> mapping;

		public MapBuilder(Class<?> type, Map<String, ViewFieldDefinition> mapping) {
			super();
			this.type = type;
			this.mapping = mapping;
		}

		@Override
		public Map<String, Object> build(Object source) {

			Map<String, Object> result = new LinkedHashMap<String, Object>();

			for (Entry<String, ViewFieldDefinition> entry : mapping.entrySet()) {
				String fieldName = entry.getValue().getField();

				Object property;

				try {
					property = getProperty(source, fieldName);
				} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
					throw new RuntimeException(e);
				}

				ViewConverterHolder holder = getConverterHolder(entry.getValue().getConverter());
				if (holder == null) {
					result.put(entry.getKey(), property);
					continue;
				}

				Context context = getContext(type, fieldName, holder.param);

				Object parsedProperty = holder.viewConverter.convert(property, context);
				result.put(entry.getKey(), parsedProperty);
			}

			return result;

		}

	}

	private Object getProperty(Object bean, String name)
			throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
		while (resolver.hasNested(name)) {
			final String next = resolver.next(name);
			Object nestedBean = null;
			nestedBean = PropertyUtils.getProperty(bean, next);
			if (nestedBean == null) {
				return null;
			}
			bean = nestedBean;
			name = resolver.remove(name);
		}

		return PropertyUtils.getProperty(bean, name);
	}

	private class ViewConverterHolder {
		IViewConverter viewConverter;
		String param;
	}

	public static class ViewFieldDefinition {
		private String field;
		private String converter;

		/**
		 * @return the field
		 */
		public String getField() {
			return field;
		}

		/**
		 * @param field
		 *            the field to set
		 */
		public void setField(String field) {
			this.field = field;
		}

		/**
		 * @return the converter
		 */
		public String getConverter() {
			return converter;
		}

		/**
		 * @param converter
		 *            the converter to set
		 */
		public void setConverter(String converter) {
			this.converter = converter;
		}

	}

	public static class ViewDefinition {

		private String type;
		private String viewName;
		private ViewFieldDefinition standalone; // 不用map，此时fields只有一个字段，将这个字段的值返回
		private Map<String, ViewFieldDefinition> mapping;

		/**
		 * @return the type
		 */
		public String getType() {
			return type;
		}

		/**
		 * @param type
		 *            the type to set
		 */
		public void setType(String type) {
			this.type = type;
		}

		/**
		 * @return the viewName
		 */
		public String getViewName() {
			return viewName;
		}

		/**
		 * @param viewName
		 *            the viewName to set
		 */
		public void setViewName(String viewName) {
			this.viewName = viewName;
		}

		/**
		 * @return the standalone
		 */
		public ViewFieldDefinition getStandalone() {
			return standalone;
		}

		/**
		 * @param standalone
		 *            the standalone to set
		 */
		public void setStandalone(ViewFieldDefinition standalone) {
			this.standalone = standalone;
		}

		/**
		 * @return the mapping
		 */
		public Map<String, ViewFieldDefinition> getMapping() {
			return mapping;
		}

		/**
		 * @param mapping
		 *            the mapping to set
		 */
		public void setMapping(Map<String, ViewFieldDefinition> mapping) {
			this.mapping = mapping;
		}

	}
}
